// bdlde_charconvertutf32.cpp                                         -*-C++-*-
#include <bdlde_charconvertutf32.h>

#include <bsls_ident.h>
BSLS_IDENT_RCSID(bdlde_charconvertutf32_cpp,"$Id$ $CSID$")

#include <bdlde_utf8util.h>    // for testing only

#include <bslmf_assert.h>     // 'BSLMF_ASSERT'
#include <bslmf_issame.h>

#include <bsls_assert.h>
#include <bsls_byteorderutil.h>
#include <bsls_types.h>

#include <bsl_algorithm.h>    // 'bsl::find'

#include <bsl_climits.h>      // 'CHAR_BIT'
#include <bsl_cstring.h>

///IMPLEMENTATION NOTES
///--------------------
// This UTF-8 documentation was copied verbatim from RFC 3629.  The original
// version was downloaded from:
//..
//     http://tools.ietf.org/html/rfc3629
//..
////////////////////////// BEGIN VERBATIM RFC TEXT //////////////////////////
// UTF-8 encodes UCS characters as a varying number of octets, where the number
// of octets, and the value of each, depend on the integer value assigned to
// the character in ISO/IEC 10646 (the character number, a.k.a. code position,
// code point or Unicode scalar value).  This encoding form has the following
// characteristics (all values are in hexadecimal):
//
// o  Character numbers from U+0000 to U+007F (US-ASCII repertoire)
//    correspond to octets 00 to 7F (7 bit US-ASCII values).  A direct
//    consequence is that a plain ASCII string is also a valid UTF-8
//    string.
//
// o  US-ASCII octet values do not appear otherwise in a UTF-8 encoded
//    character stream.  This provides compatibility with file systems
//    or other software (e.g., the printf() function in C libraries)
//    that parse based on US-ASCII values but are transparent to other
//    values.
//
// o  Round-trip conversion is easy between UTF-8 and other encoding
//    forms.
//
// o  The first octet of a multi-octet sequence indicates the number of
//    octets in the sequence.
//
// o  The octet values C0, C1, F5 to FF never appear.
//
// o  Character boundaries are easily found from anywhere in an octet
//    stream.
//
// o  The byte-value lexicographic sorting order of UTF-8 strings is the
//    same as if ordered by character numbers.  Of course this is of
//    limited interest since a sort order based on character numbers is
//    almost never culturally valid.
//
// o  The Boyer-Moore fast search algorithm can be used with UTF-8 data.
//
// o  UTF-8 strings can be fairly reliably recognized as such by a
//    simple algorithm, i.e., the probability that a string of
//    characters in any other encoding appears as valid UTF-8 is low,
//    diminishing with increasing string length.
//
// UTF-8 was devised in September 1992 by Ken Thompson, guided by design
// criteria specified by Rob Pike, with the objective of defining a UCS
// transformation format usable in the Plan9 operating system in a non-
// disruptive manner.  Thompson's design was stewarded through standardization
// by the X/Open Joint Internationalization Group XOJIG (see [FSS_UTF]),
// bearing the names FSS-UTF (variant FSS/UTF), UTF-2 and finally UTF-8 along
// the way.
//
// ...
//
// UTF-8 is defined by the Unicode Standard [UNICODE].  Descriptions and
// formulae can also be found in Annex D of ISO/IEC 10646-1 [ISO.10646]
//
// In UTF-8, characters from the U+0000..U+10FFFF range (the UTF-16 accessible
// range) are encoded using sequences of 1 to 4 octets.  The only octet of a
// "sequence" of one has the higher-order bit set to 0, the remaining 7 bits
// being used to encode the character number.  In a sequence of n octets, n>1,
// the initial octet has the n higher-order bits set to 1, followed by a bit
// set to 0.  The remaining bit(s) of that octet contain bits from the number
// of the character to be encoded.  The following octet(s) all have the
// higher-order bit set to 1 and the following bit set to 0, leaving 6 bits in
// each to contain bits from the character to be encoded.
//
// The table below summarizes the format of these different octet types.  The
// letter x indicates bits available for encoding bits of the character number.
//
// Char number range   |        UTF-8 octet sequence
//    (hexadecimal)    |              (binary)
//  -------------------+---------------------------------------------
//  000 0000-0000 007F | 0xxxxxxx
//  000 0080-0000 07FF | 110xxxxx 10xxxxxx
//  000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
//  001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
/////////////////////////// END VERBATIM RFC TEXT ///////////////////////////

// UTF-32 encoding is straightforward -- one 'unsigned int' *word* of UTF-32
// corresponds to one code point of Unicode.  Values must be in the range
// '[0 .. 0xd7ff]' or in the range '[ 0xe000 .. 0x10ffff ]'.

namespace {

// TYPES

/// `char` often defaults to a signed type, which becomes problematic in
/// widening to 32 bit values, so we use this unsigned type to store bytes
/// of UTF-8.
typedef unsigned char OctetType;

// Portability/sanity checks -- data type sizes.
BSLMF_ASSERT(8 == CHAR_BIT);
BSLMF_ASSERT(4 == sizeof(unsigned int));
BSLMF_ASSERT(sizeof(char) == sizeof(OctetType));

enum {
    // The return values of all the public functions defined by this component
    // return a bit-wise Or of these two flags.  When the output is to a
    // container rather than a fixed-length buffer, the 'k_OUT_OF_SPACE_BIT' is
    // never set.

    k_INVALID_INPUT_BIT =
                    BloombergLP::bdlde::CharConvertStatus::k_INVALID_INPUT_BIT,
    k_OUT_OF_SPACE_BIT  =
                    BloombergLP::bdlde::CharConvertStatus::k_OUT_OF_SPACE_BIT
};

enum Utf8Bits {
    // Masks and shifts used to assemble and dismantle UTF-8 encodings.

    k_ONE_OCT_CONT_WID   = 7,               // content in a one-octet coding
    k_ONE_OCTET_MASK     = 0xff & (~0u << k_ONE_OCT_CONT_WID),

    k_CONTINUE_CONT_WID  = 6,               // content in a continuation
                                          //                      octet
    k_CONTINUE_MASK      = 0xff & (~0u << k_CONTINUE_CONT_WID),
    k_CONTINUE_TAG       = k_ONE_OCTET_MASK,  // compare this to masked bits

    k_TWO_OCT_CONT_WID   = 5,               // content in a two-octet header
    k_TWO_OCTET_MASK     = 0xff & (~0u << k_TWO_OCT_CONT_WID),
    k_TWO_OCTET_TAG      = k_CONTINUE_MASK,   // compare this to masked bits

    k_THREE_OCT_CONT_WID = 4,               // content in a 3-octet header
    k_THREE_OCTET_MASK   = 0xff & (~0u << k_THREE_OCT_CONT_WID),
    k_THREE_OCTET_TAG    = k_TWO_OCTET_MASK,  // compare this to masked bits

    k_FOUR_OCT_CONT_WID  = 3,               // content in a four-octet header
    k_FOUR_OCTET_MASK    = 0xff & (~0u << k_FOUR_OCT_CONT_WID),
    k_FOUR_OCTET_TAG     = k_THREE_OCTET_MASK // compare this to masked bits
};

                           // =====================
                           // local struct Capacity
                           // =====================

/// Functor passed to `Utf8ToUtf32Translator` and `Utf32ToUtf8Translator` in
/// cases where we monitor capacity available in output.  Initialize in
/// c'tor with an integer `capacity`, then thereafter support operators
/// `--`, `-=`, and `<` for that value.
struct Capacity {

    bsl::size_t d_capacity;

    // CREATORS

    /// Create a `Capacity` object with the specified `capacity`.
    explicit
    Capacity(bsl::size_t capacity);

    // MANIPULATORS

    /// Decrement `d_capacity`.
    void operator--();

    /// Decrement `d_capacity` by the specified `delta`.
    void operator-=(int delta);

    // ACCESSORS

    /// Return `true` if `d_capacity` is less than the specified `rhs`, and
    /// `false` otherwise.
    bool operator<( bsl::size_t rhs) const;

    /// Return `true` if `d_capacity` is greater than or equal to the
    /// specified `rhs`, and `false` otherwise.
    bool operator>=(bsl::size_t rhs) const;
};

                           // ---------------------
                           // local struct Capacity
                           // ---------------------

// CREATOR
inline
Capacity::Capacity(bsl::size_t capacity)
: d_capacity(capacity)
{}

// MANIPULATORS

/// Decrement `d_capacity`.
inline
void Capacity::operator--()
{
    --d_capacity;
}

/// Decrement `d_capacity` by `delta`.
inline
void Capacity::operator-=(int delta)
{
    d_capacity -= delta;
}

// ACCESSORS

/// Return `true` if `d_capacity` is less than the specified `rhs`, and
/// `false` otherwise.
inline
bool Capacity::operator<( bsl::size_t rhs) const
{
    return d_capacity <  rhs;
}

/// Return `true` if `d_capacity` is greater than or equal to the specified
/// `rhs`, and `false` otherwise.
inline
bool Capacity::operator>=(bsl::size_t rhs) const
{
    return d_capacity >= rhs;
}

                         // =========================
                         // local struct NoopCapacity
                         // =========================

/// Functor passed to `Utf8ToUtf32Translator` and `Utf32ToUtf8Translator` in
/// cases where we don't want to monitor capacity available in output, all
/// operations on this object are to become no-ops, the `<` and `>=`
/// operators return constant `bool` values to indicate that adequate
/// capacity is available.
struct NoopCapacity {

    // CREATORS

    /// Create empty object.
    explicit
    NoopCapacity(bsl::size_t);

    // MANIPULATORS

    /// No-op.
    void operator--();

    /// No-op.
    void operator-=(int);

    // ACCESSORS

    /// Return `false`.
    bool operator<( bsl::size_t) const;

    /// Return `true`.
    bool operator>=(bsl::size_t) const;
};

                         // -------------------------
                         // local struct NoopCapacity
                         // -------------------------

// CREATOR

/// Create empty object.
inline
NoopCapacity::NoopCapacity(bsl::size_t)
{}

// MANIPULATORS

/// No-op.
inline
void NoopCapacity::operator--()
{}

/// No-op.
inline
void NoopCapacity::operator-=(int)
{}

// ACCESSORS

/// Return `false`.
inline
bool NoopCapacity::operator<( bsl::size_t) const
{
    return false;
}

/// Return `true`.
inline
bool NoopCapacity::operator>=(bsl::size_t) const
{
    return true;
}

                            // ====================
                            // local struct Swapper
                            // ====================

/// This `struct` serves as a template argument.  The type is used for
/// reversing the byte order of `unsigned int` values passed to `swapBytes`.
struct Swapper {

    // CLASS METHODS

    /// Return the specified `x` with its byte order reversed;
    static unsigned int swapBytes(unsigned int x);
};

inline
unsigned int Swapper::swapBytes(unsigned int x)
{
    return BloombergLP::bsls::ByteOrderUtil::swapBytes(x);
}

                          // ========================
                          // local struct NoopSwapper
                          // ========================

/// This `struct` serves as a template argument, to be substituted for
/// `Swapper` when swapping is not desired.  The type is used for returning
/// the value passed to `swapBytes` without modification.  Note that the
/// function name and signature must match that of the function in
/// `Swapper`.
struct NoopSwapper {

    // CLASS METHODS

    /// Return the specified `x` without modification.
    static unsigned int swapBytes(unsigned int x);
};

inline
unsigned int NoopSwapper::swapBytes(unsigned int x)
{
    return x;
}

                        // ===========================
                        // local class Utf8PtrBasedEnd
                        // ===========================

/// This `class` is initialized with a pointer to the end of input.  The
/// `isFinished` function just determines whether input has reached that
/// end by comparing pointers.
class Utf8PtrBasedEnd {

    // DATA
    const OctetType * const d_end;

  public:
    // CREATORS

    /// Create a `Utf8PtrBasedEnd` object with the end at the specified
    /// `end`.
    explicit
    Utf8PtrBasedEnd(const char *end);

    // ACCESSORS

    /// Return `true` if the specified `position` is at the end of input and
    /// `false` otherwise.  The behavior is undefined unless
    /// `position <= d_end`.
    bool isFinished(const OctetType *position) const;

    /// Return a pointer to after the specified `skipBy` consecutive
    /// continuation bytes following the specified `octets` that are prior
    /// to `d_end`.  The behavior is undefined unless `octets <= d_end`.
    const OctetType *skipContinuations(const OctetType *octets,
                                       int              skipBy) const;

    /// Return `true` if there are at least the specified `n` continuation
    /// bytes beginning at the specified `octets` and prior to `d_end`, and
    /// `false` otherwise.  The behavior is undefined if `octets` is past
    /// the end.  The behavior is undefined unless `octets <= d_end`.
    bool verifyContinuations(const OctetType *octets, int n) const;
};

                        // ---------------------------
                        // local class Utf8PtrBasedEnd
                        // ---------------------------

// CREATORS
inline
Utf8PtrBasedEnd::Utf8PtrBasedEnd(const char *end)
: d_end(reinterpret_cast<const OctetType *>(end))
{}

// ACCESSORS
inline
bool Utf8PtrBasedEnd::isFinished(const OctetType *position) const
{
    if (position < d_end) {
        return false;                                                 // RETURN
    }
    else {
        BSLS_ASSERT(d_end == position);
        return true;                                                  // RETURN
    }
}

inline
const OctetType *Utf8PtrBasedEnd::skipContinuations(
                                                 const OctetType *octets,
                                                 int              skipBy) const
{
    // This function is not called in the critical path.  It is called when it
    // is known that there are fewer continuation octets after 'octets' than
    // were expected, at least before 'd_end'.

    BSLS_ASSERT(d_end >= octets);
    const OctetType *end = bsl::min(d_end, octets + skipBy);

    while (octets < end && (*octets & k_CONTINUE_MASK) == k_CONTINUE_TAG) {
        ++octets;
    }

    return octets;
}

inline
bool Utf8PtrBasedEnd::verifyContinuations(const OctetType *octets,
                                          int              n) const
{
    BSLS_ASSERT(n >= 1);
    BSLS_ASSERT(d_end >= octets);

    const OctetType *end = octets + n;
    if (end > d_end) {
        return false;                                                 // RETURN
    }

    do {
        if ((*octets & k_CONTINUE_MASK) != k_CONTINUE_TAG) {
            return false;                                             // RETURN
        }

        ++octets;
    } while (octets < end);

    return true;
}

                       // =============================
                       // local struct Utf8ZeroBasedEnd
                       // =============================

struct Utf8ZeroBasedEnd {
    // CREATORS

    /// Create a `Utf8ZeroBasedEnd` object.
    Utf8ZeroBasedEnd();

    // ACCESSORS

    /// Return `true` if the specified `position` is at the end of input,
    /// and `false` otherwise.
    bool isFinished(const OctetType *position) const;

    /// Return a pointer to after up to the specified `skipBy` consecutive
    /// continuation bytes following the specified `octets`.  The function
    /// will skip over less than `skipBy` octets if it encounters end of
    /// input, or any non-continuation octets.  The behavior is undefined
    /// unless `octets` is before or at the end of input.
    const OctetType *skipContinuations(const OctetType *octets,
                                       int              skipBy) const;

    /// Return `true` if there are at least the specified `n` continuation
    /// bytes beginning at the specified `octets` and `false` otherwise.
    /// The behavior is undefined unless `n >= 1`.
    bool verifyContinuations(const OctetType *octets, int n) const;
};

                       // -----------------------------
                       // local struct Utf8ZeroBasedEnd
                       // -----------------------------

// CREATORS
inline
Utf8ZeroBasedEnd::Utf8ZeroBasedEnd()
{
}

// ACCESSORS
inline
bool Utf8ZeroBasedEnd::isFinished(const OctetType *position) const
{
    return 0 == *position;
}

inline
const OctetType *Utf8ZeroBasedEnd::skipContinuations(
                                                 const OctetType *octets,
                                                 int              skipBy) const
{
    // This function is not called in the critical path.  It is called when it
    // is known that there are not as many continuation octets after 'octets'
    // as were expected.  Note that if '0 == *octets' the loop will stop.

    const OctetType *end = octets + skipBy;

    while (octets < end && (*octets & k_CONTINUE_MASK) == k_CONTINUE_TAG) {
        ++octets;
    }

    return octets;
}

inline
bool Utf8ZeroBasedEnd::verifyContinuations(const OctetType *octets,
                                           int              n) const
{
    BSLS_ASSERT(n >= 1);

    const OctetType *end = octets + n;
    do {
        if ((*octets & k_CONTINUE_MASK) != k_CONTINUE_TAG) {
            return false;                                             // RETURN
        }

        ++octets;
    } while (octets < end);

    return true;
}


                        // ============================
                        // local class Utf32PtrBasedEnd
                        // ============================

/// This `class` is initialized with a pointer to the end of input.  The
/// `isFinished` function just determines whether input has reached that
/// end by comparing pointers.
class Utf32PtrBasedEnd {

    // DATA
    const unsigned int * const d_end_p;

  public:
    // CREATORS

    /// Create a `Utf8PtrBasedEnd` object with the end at the specified
    /// `end`.
    explicit
    Utf32PtrBasedEnd(const unsigned int *end);

    // ACCESSORS

    /// Return `true` if the specified `position` is at the end of input and
    /// `false` otherwise.  The behavior is undefined unless
    /// `position <= d_end`.
    bool isFinished(const unsigned int *position) const;
};

                        // ---------------------------
                        // local class Utf8PtrBasedEnd
                        // ---------------------------

// CREATORS
inline
Utf32PtrBasedEnd::Utf32PtrBasedEnd(const unsigned int *end)
: d_end_p(end)
{}

// ACCESSORS
inline
bool Utf32PtrBasedEnd::isFinished(const unsigned int *position) const
{
    if (position < d_end_p) {
        return false;                                                 // RETURN
    }
    else {
        BSLS_ASSERT(d_end_p == position);
        return true;                                                  // RETURN
    }
}

                       // ==============================
                       // local struct Utf32ZeroBasedEnd
                       // ==============================

struct Utf32ZeroBasedEnd {
    // CREATORS

    /// Create a `Utf32ZeroBasedEnd` object.
    Utf32ZeroBasedEnd();

    // ACCESSORS

    /// Return `true` if the specified `position` is at the end of input,
    /// and `false` otherwise.
    bool isFinished(const unsigned int *position) const;
};

                       // ------------------------------
                       // local struct Utf32ZeroBasedEnd
                       // ------------------------------

// CREATORS
inline
Utf32ZeroBasedEnd::Utf32ZeroBasedEnd()
{
}

// ACCESSORS
inline
bool Utf32ZeroBasedEnd::isFinished(const unsigned int *position) const
{
    return 0 == *position;
}

}  // close unnamed namespace

/// Return the specified `ptr` cast to a `const OctetType *`.  Note that
/// `static_cast` does not work here, and the idea is to be sure in these
/// casts that one is never accidentally casting between pointers to `char`
/// or `OctetType` and pointers to `unsigned int`.
static inline
const OctetType *constOctetCast(const char *ptr)
{
    BSLMF_ASSERT(sizeof(*ptr) == sizeof(OctetType));
    return reinterpret_cast<const OctetType *>(ptr);
}

/// Return the specified `ptr` cast to an `OctetType *`.  Note that
/// `static_cast` does not work here, and the idea is to be sure in these
/// casts that one is never accidentally casting between pointers to `char`
/// or `OctetType` and pointers to `unsigned int`.
static inline
OctetType *octetCast(char *ptr)
{
    BSLMF_ASSERT(sizeof(*ptr) == sizeof(OctetType));
    return reinterpret_cast<OctetType *>(ptr);
}

/// Return `true` if the specified `oct` is a single-octet UTF-8 sequence.
static inline
bool isSingleOctet(OctetType oct)
{
    return ! (oct & k_ONE_OCTET_MASK);
}

/// Return `true` if the specified `oct` is a continuation octet and `false`
/// otherwise.
static inline
bool isContinuation(OctetType oct)
{
    return (oct & k_CONTINUE_MASK) == k_CONTINUE_TAG;
}

/// Return `true` if the specified `oct` is the first octet of a two-octet
/// UTF-8 sequence and `false` otherwise.
static inline
bool isTwoOctetHeader(OctetType oct)
{
    return (oct & k_TWO_OCTET_MASK) == k_TWO_OCTET_TAG;
}

/// Return `true` if the specified `oct` is the first octet of a three-octet
/// UTF-8 sequence and `false` otherwise.
static inline
bool isThreeOctetHeader(OctetType oct)
{
    return (oct & k_THREE_OCTET_MASK) == k_THREE_OCTET_TAG;
}

/// Return `true` if the specified `oct` is the first octet of a four-octet
/// UTF-8 sequence and `false` otherwise.
static inline
bool isFourOctetHeader(OctetType oct)
{
    return (oct & k_FOUR_OCTET_MASK) == k_FOUR_OCTET_TAG;
}

/// Return the value of the two-octet sequence that begins with the octet
/// pointed to by the specified `octBuf`.
static inline
unsigned int decodeTwoOctets(const OctetType *octBuf)
{
    return     (octBuf[1] & ~k_CONTINUE_MASK)
            | ((octBuf[0] & ~k_TWO_OCTET_MASK)   <<     k_CONTINUE_CONT_WID);
}

/// Return the value of the three-octet sequence that begins with the octet
/// pointed to by the specified `octBuf`.
static inline
unsigned int decodeThreeOctets(const OctetType *octBuf)
{
    return     (octBuf[2] & ~k_CONTINUE_MASK)
            | ((octBuf[1] & ~k_CONTINUE_MASK)    <<     k_CONTINUE_CONT_WID)
            | ((octBuf[0] & ~k_THREE_OCTET_MASK) << 2 * k_CONTINUE_CONT_WID);
}

/// Return the value of the four-octet sequence that begins with the octet
/// pointed to by the specified `octBuf`.
static inline
unsigned int decodeFourOctets(const OctetType *octBuf)
{
    return     (octBuf[3] & ~k_CONTINUE_MASK)
            | ((octBuf[2] & ~k_CONTINUE_MASK)   <<     k_CONTINUE_CONT_WID)
            | ((octBuf[1] & ~k_CONTINUE_MASK)   << 2 * k_CONTINUE_CONT_WID)
            | ((octBuf[0] & ~k_FOUR_OCTET_MASK) << 3 * k_CONTINUE_CONT_WID);
}

/// Return the number of continuation octets beginning at the specified
/// `octBuf`, up to but not greater than the specified `n`.  Note that a
/// null octet is not a continuation and is taken to end the scan.
static inline
bsl::size_t lookaheadContinuations(const OctetType * const octBuf, int n)
{
    const OctetType *       pc  = octBuf;
    const OctetType * const end = pc + n;
    while (pc < end && isContinuation(*pc)) {
        ++pc;
    }
    return pc - octBuf;
}

/// Return `true` if the specified Unicode value `uc` can be coded in a
/// single UTF-8 octet and `false` otherwise.
static inline
bool fitsInSingleOctet(unsigned int uc)
{
    return 0 == (uc & (~ (unsigned int) 0 << k_ONE_OCT_CONT_WID));
}

/// Return `true` if the specified Unicode value `uc` can be coded in two
/// UTF-8 octets or less and `false` otherwise.
static inline
bool fitsInTwoOctets(unsigned int uc)
{
    return 0 == (uc & (~ (unsigned int) 0 << (k_TWO_OCT_CONT_WID +
                                                      k_CONTINUE_CONT_WID)));
}

/// Return `true` if the specified Unicode value `uc` can be coded in three
/// UTF-8 octets or less and `false` otherwise.
static inline
bool fitsInThreeOctets(unsigned int uc)
{
    return 0 == (uc & (~ (unsigned int) 0 << (k_THREE_OCT_CONT_WID +
                                                  2 * k_CONTINUE_CONT_WID)));
}

/// Return `true` if the specified Unicode value `uc` can be coded in four
/// UTF-8 octets or less and `false` otherwise.
static inline
bool fitsInFourOctets(unsigned int uc)
{
    return 0 == (uc & (~ (unsigned int) 0 << (k_FOUR_OCT_CONT_WID +
                                                  3 * k_CONTINUE_CONT_WID)));
}

/// Encode the specified Unicode value `isoBuf` into the two bytes pointed
/// at by the specified `octBuf`.
static inline
void encodeTwoOctets(OctetType *octBuf, unsigned int isoBuf)
{
    octBuf[1] = (OctetType) (k_CONTINUE_TAG | (isoBuf & ~k_CONTINUE_MASK));
    octBuf[0] = (OctetType) (k_TWO_OCTET_TAG |
                 ((isoBuf >>     k_CONTINUE_CONT_WID) & ~k_TWO_OCTET_MASK));
}

/// Encode the specified Unicode value `isoBuf` into the three bytes pointed
/// at by the specified `octBuf`.
static inline
void encodeThreeOctets(OctetType *octBuf, unsigned int isoBuf)
{
    octBuf[2] = (OctetType) (k_CONTINUE_TAG | (isoBuf & ~k_CONTINUE_MASK));
    octBuf[1] = (OctetType) (k_CONTINUE_TAG |
                 ((isoBuf >>     k_CONTINUE_CONT_WID) & ~k_CONTINUE_MASK));
    octBuf[0] = (OctetType) (k_THREE_OCTET_TAG |
                 ((isoBuf >> 2 * k_CONTINUE_CONT_WID) & ~k_THREE_OCTET_MASK));
}

/// Encode the specified Unicode value `isoBuf` into the four bytes pointed
/// at by the specified `octBuf`.
static inline
void encodeFourOctets(OctetType *octBuf, unsigned int isoBuf)
{
    octBuf[3] = (OctetType) (k_CONTINUE_TAG | (isoBuf & ~k_CONTINUE_MASK));
    octBuf[2] = (OctetType) (k_CONTINUE_TAG |
                 ((isoBuf >>     k_CONTINUE_CONT_WID) & ~k_CONTINUE_MASK));
    octBuf[1] = (OctetType) (k_CONTINUE_TAG |
                 ((isoBuf >> 2 * k_CONTINUE_CONT_WID) & ~k_CONTINUE_MASK));
    octBuf[0] = (OctetType) (k_FOUR_OCTET_TAG |
                 ((isoBuf >> 3 * k_CONTINUE_CONT_WID) & ~k_FOUR_OCTET_MASK));
}

/// Return `true` if the specified Unicode value `uc` is a value reserved
/// for the encoding of double-word planes in UTF-16 (such values are
/// illegal in ANY Unicode format) and `false` otherwise.
static inline
bool isIllegal16BitValue(unsigned int uc)
{
    return uc >= 0xd800 && uc < 0xe000;
}

/// Return `true` if the specified 32-bit value `uc` is too high to be
/// represented in Unicode and `false` otherwise.
static inline
bool isIllegalFourOctetValue(unsigned int uc)
{
    return uc > 0x10ffff;
}

#if defined(BSLS_ASSERT_IS_USED)
static inline
bool isLegalUtf32ErrorWord(unsigned int uc)
    // Return 'true' if the specified 32-bit value 'uc' is legal to be
    // represented in Unicode and 'false' otherwise.
{
    return uc < 0xd800 || (uc >= 0xe000 && uc <= 0x10ffff);
}
#endif

/// Return a pointer to the next Unicode code point after the code point in
/// the sequence beginning at the specified `input`.  Note that an
/// incomplete sequence is skipped as a single char, and that any first byte
/// that is neither a single byte nor a header of a valid UTF-8 sequence is
/// interpreted as a 5-byte header.
static inline
const OctetType *skipUtf8CodePoint(const OctetType *input)
{
    const OctetType uc = *input;

    if (isSingleOctet(uc)) {
        return input + 1;                                             // RETURN
    }

    int expected = isTwoOctetHeader(uc)
                 ? 1
                 : isThreeOctetHeader(uc)
                   ? 2
                   : isFourOctetHeader(uc)
                     ? 3
                     : 4;
    ++input;
    return input + lookaheadContinuations(input, expected);
}

/// Return the number of `unsigned int`s sufficient to store the UTF-8
/// sequence beginning at the specified `input`, including the terminating 0
/// word of the output.  Use the specified `endFunctor` to determine end of
/// input.  Note that if the translation occurs with 0 specified as the
/// error word and errors are present, this will be an over-estimate,
/// otherwise the result will be exact.  Also note that the end of `input`
/// is determined by `endFunctor`, which will treat the input as either
/// null-terminated or fixed-length.
template <class END_FUNCTOR>
static
bsl::size_t utf32BufferLengthNeeded(const char  *input,
                                    END_FUNCTOR  endFunctor)
{
    // Note that to exactly calculate the size would require much more detailed
    // decoding and would hence be much slower.  Also, the default is for the
    // error word not to be 0, in which case this estimate will be exact.
    // Also, error sequences will usually range from extremely rare to totally
    // not present.  For these reasons, this faster, less exact algorithm was
    // chosen.

    BSLMF_ASSERT((bsl::is_same<END_FUNCTOR,  Utf8PtrBasedEnd>::value ||
                  bsl::is_same<END_FUNCTOR, Utf8ZeroBasedEnd>::value));

    const OctetType *octets = constOctetCast(input);

    bsl::size_t ret = 0;
    for (; ! endFunctor.isFinished(octets); ++ret) {
        octets = skipUtf8CodePoint(octets);
    }

    return ret + 1;
}

/// Return the length, in bytes, of the UTF-8 sequence required to store the
/// translation of the UTF-32 sequence pointed at by the specified `input`,
/// including the terminating 0, when using the specified `errorByte`.  Use
/// the specified `endFunctor` on `input` prior to dereferencing it to
/// detect end for input.  Use the specified `SWAPPER` to perform swapping,
/// or not perform swapping, as desired (see detailed doc in
/// `Utf8ToUtf32Translator` and `Utf32ToUtf8Translator` below).  Note that
/// this estimate will always be exact.
template <class END_FUNCTOR, class SWAPPER>
static
bsl::size_t utf8BufferLengthNeeded(const unsigned int *input,
                                   END_FUNCTOR         endFunctor,
                                   const OctetType     errorByte)
{
    BSLMF_ASSERT((bsl::is_same<END_FUNCTOR,  Utf32PtrBasedEnd>::value ||
                  bsl::is_same<END_FUNCTOR, Utf32ZeroBasedEnd>::value));

    BSLMF_ASSERT((bsl::is_same<SWAPPER,     Swapper>::value ||
                  bsl::is_same<SWAPPER, NoopSwapper>::value));

    unsigned errorByteSize = !!errorByte;

    unsigned int uc;
    bsl::size_t ret = 0;
    for (; !endFunctor.isFinished(input); ++input) {
        uc = SWAPPER::swapBytes(*input);
        ret += fitsInSingleOctet(uc)
               ? 1
               : fitsInTwoOctets(uc)
                 ? 2
                 : fitsInThreeOctets(uc)
                   ? (isIllegal16BitValue(uc)
                      ? errorByteSize
                      : 3)
                   : fitsInFourOctets(uc)
                     ? (isIllegalFourOctetValue(uc)
                        ? errorByteSize
                        : 4)
                     : errorByteSize;
    }

    return ret + 1;
}

namespace {
    // Second unnamed namespace -- the static routines that precede this use
    // constants from the first unnamed namespace.  Now we are declaring two
    // local classes, 'Utf8ToUtf32Translator' and 'Utf32ToUtf8Translator', that
    // use those static routines.

                     // =================================
                     // local class Utf8ToUtf32Translator
                     // =================================

/// This `class` is used to create an instance of an object that will be
/// used for translating a UTF-8 stream into UTF-32.  All functions in this
/// `class` are private except for a single static method, `translate`,
/// which creates an object of this type and calls manipulators on it.
///
/// The template argument `CAPACITY` is to be either of the `Capacity` or of
/// the `NoopCapacity` type defined in the first unnamed namespace.  When
/// the output is to a buffer of fixed length that may not be adequate, the
/// `Capacity` type is used and calculations are made incrementally to
/// constantly check if the end of the buffer has been reached.  If the
/// destination buffer is known ahead of time to be long enough, the
/// `NoopCapacity` type should be used, and then all the incremental
/// capacity checking becomes inline calls to no-op functions that will
/// generate no code and waste no CPU time.
///
/// The template argument `END_FUNCTOR` is used for determining the end of
/// input.  If the input is null terminated, `Utf8ZeroBasedEnd` is used for
/// `END_FUNCTOR`; if the input is of fixed length, an instance of
/// `Utf8PtrBasedEnd`, which contains a pointer to the end of the string, is
/// used.
///
/// The template argument `SWAPPER` is use for handling the byte order of
/// the UTF-32 output.  If the output is in host byte order, the
/// `NoopSwapper` type is used, which does no swapping.  If the output is in
/// the opposite of host byte order, the `Swapper` type is used, which will
/// swap the byte order of words as they are output.
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
class Utf8ToUtf32Translator {

    BSLMF_ASSERT((bsl::is_same<CAPACITY,            Capacity>::value ||
                  bsl::is_same<CAPACITY,        NoopCapacity>::value));

    BSLMF_ASSERT((bsl::is_same<END_FUNCTOR,  Utf8PtrBasedEnd>::value ||
                  bsl::is_same<END_FUNCTOR, Utf8ZeroBasedEnd>::value));

    BSLMF_ASSERT((bsl::is_same<SWAPPER,              Swapper>::value ||
                  bsl::is_same<SWAPPER,          NoopSwapper>::value));

    // DATA
    unsigned int     *d_output;
    CAPACITY          d_capacity;
    END_FUNCTOR       d_endFunctor;
    const OctetType  *d_input;
    unsigned int      d_swappedErrorWord;   // 'SWAPPER::swap' is applied to
                                            // the 'errorWord' arg to the c'tor
                                            // and the result stored to this
                                            // variable.
    bool              d_invalidSequences;

  private:
    // PRIVATE CREATORS

    /// Create a `Utf8ToUtf32Translator` object to translate UTF-8 from the
    /// specified `input` to be written as UTF-32 to the specified `output`,
    /// that has the specified `capacity` `unsigned int`s of room in it.
    /// Use the specified `endFunctor` for evaluating continuation octets
    /// and determining end of input.  When an error is encountered in the
    /// input, output the specified `errorWord` to the output unless
    /// `errorWord` is 0, in which case no output corresponding to the error
    /// sequence is generated.  Note that `errorWord` is assumed to be in
    /// host byte order.
    Utf8ToUtf32Translator(unsigned int    *output,
                          bsl::size_t      capacity,
                          END_FUNCTOR      endFunctor,
                          const OctetType *input,
                          unsigned int     errorWord);

    // PRIVATE MANIPULATORS

    /// Update the output pointer and the capacity of this object to reflect
    /// the fact that a word of output has been written.
    void advanceOutput();

    /// Update the state of this object and possibly the output to reflect
    /// that an error sequence was encountered in the input.  The behavior
    /// is undefined unless `d_capacity >= 2`.
    void handleInvalidSequence();

    /// Read one Unicode code point of UTF-8 from the input stream
    /// `d_input`, and update the output and the state of this object
    /// accordingly.  Return a non-zero value if there was insufficient
    /// capacity for the output, and 0 otherwise.  The behavior is undefined
    /// unless at least 1 word of space is available in the output buffer.
    int decodeCodePoint();

  public:
    // CLASS METHODS

    /// Translate a UTF-8 stream from the specified null-terminated `input`
    /// to a UTF-32 stream written to the buffer at the specified `output`,
    /// always null-terminating the result.  If the template argument is
    /// type `Capacity`, the output buffer is the specified `capacity` words
    /// long, and encode as many Unicode code points as will fit, including
    /// the terminating null code point.  If the template argument is type
    /// `NoopCapacity`, `capacity` is ignored, the output buffer is assumed
    /// to be long enough, and the entire UTF-32 sequence is to be
    /// translated.  Use the specified `endFunctor` to determine end of
    /// input.  Write to the specified `*numWordsWritten` the number of
    /// Unicode code points written, including the terminating null
    /// code point.  If the specified `errorWord` is non-zero, write
    /// `errorWord` to the output every time an error code point is
    /// encountered in the input; otherwise write no output corresponding to
    /// error code points in the input.  The behavior is undefined unless
    /// `CAPACITY` is `NoopCapacity` or `capacity > 0`.
    static
    int translate(unsigned int      *output,
                  bsl::size_t        capacity,
                  END_FUNCTOR        endFunctor,
                  const char        *input,
                  bsl::size_t       *numWordsWritten,
                  unsigned int       errorWord);
};

                     // ---------------------------------
                     // local class Utf8ToUtf32Translator
                     // ---------------------------------

// PRIVATE CREATORS
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
inline
Utf8ToUtf32Translator<CAPACITY, END_FUNCTOR, SWAPPER>::Utf8ToUtf32Translator(
                                                   unsigned int    *output,
                                                   bsl::size_t      capacity,
                                                   END_FUNCTOR      endFunctor,
                                                   const OctetType *input,
                                                   unsigned int     errorWord)
: d_output(output)
, d_capacity(capacity)
, d_endFunctor(endFunctor)
, d_input(input)
, d_swappedErrorWord(SWAPPER::swapBytes(errorWord))
, d_invalidSequences(false)
{}

// PRIVATE MANIPULATORS
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
inline
void Utf8ToUtf32Translator<CAPACITY, END_FUNCTOR, SWAPPER>::advanceOutput()
{
    ++d_output;
    --d_capacity;
}

template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
int Utf8ToUtf32Translator<CAPACITY, END_FUNCTOR, SWAPPER>::decodeCodePoint()
{
    BSLS_ASSERT(d_capacity >= 1);

    OctetType    firstOctet = *d_input;
    unsigned int decodedCodePoint;
    int          len;
    bool         good;

    if (d_capacity < 2) {
        return -1;                                                    // RETURN
    }

    if      (isSingleOctet(     firstOctet)) {
        len = 1;
        good = true;
        decodedCodePoint = firstOctet;
    }
    else if (isTwoOctetHeader(  firstOctet)) {
        len = 2;
        good = d_endFunctor.verifyContinuations(d_input + 1, 1) &&
              ! fitsInSingleOctet(decodedCodePoint = decodeTwoOctets(d_input));
    }
    else if (isThreeOctetHeader(firstOctet)) {
        len = 3;
        good = d_endFunctor.verifyContinuations(d_input + 1, 2) &&
            ! fitsInTwoOctets(decodedCodePoint = decodeThreeOctets(d_input)) &&
                                       ! isIllegal16BitValue(decodedCodePoint);
    }
    else if (isFourOctetHeader( firstOctet)) {
        len = 4;
        good = d_endFunctor.verifyContinuations(d_input + 1, 3) &&
           ! fitsInThreeOctets(decodedCodePoint = decodeFourOctets(d_input)) &&
                                   ! isIllegalFourOctetValue(decodedCodePoint);
    }
    else {
        len = 5;
        good = false;
    }

    if (good) {
        d_input += len;

        *d_output = SWAPPER::swapBytes(decodedCodePoint);
        advanceOutput();
    }
    else {
        d_input = d_endFunctor.skipContinuations(d_input + 1, len - 1);

        handleInvalidSequence();
    }

    return 0;
}

template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
inline
void
Utf8ToUtf32Translator<CAPACITY, END_FUNCTOR, SWAPPER>::handleInvalidSequence()
{
    BSLS_ASSERT(d_capacity >= 2);

    d_invalidSequences = true;

    if (d_swappedErrorWord) {
        *d_output = d_swappedErrorWord;
        advanceOutput();
    }
}

// CLASS METHODS
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
int Utf8ToUtf32Translator<CAPACITY, END_FUNCTOR, SWAPPER>::translate(
                                                 unsigned int *output,
                                                 bsl::size_t   capacity,
                                                 END_FUNCTOR   endFunctor,
                                                 const char   *input,
                                                 bsl::size_t  *numWordsWritten,
                                                 unsigned int  errorWord)
{
    Utf8ToUtf32Translator<CAPACITY, END_FUNCTOR, SWAPPER> translator(
                                                         output,
                                                         capacity,
                                                         endFunctor,
                                                         constOctetCast(input),
                                                         errorWord);
    BSLS_ASSERT(translator.d_capacity >= 1);

    int ret = 0;
    while (!endFunctor.isFinished(translator.d_input)) {
        if (0 != translator.decodeCodePoint()) {
            BSLS_ASSERT((bsl::is_same<CAPACITY, Capacity>::value));
            ret = k_OUT_OF_SPACE_BIT;
            break;
        }
    }
    ++translator.d_input;
    BSLS_ASSERT(translator.d_capacity >= 1);

    *translator.d_output = 0;
    translator.advanceOutput();

    bsl::size_t ncw = translator.d_output - output;
    *numWordsWritten = ncw;
    BSLS_ASSERT((bsl::is_same<CAPACITY, NoopCapacity>::value) ||
                                                              ncw <= capacity);
    BSLS_ASSERT(ncw <=
                   (bsl::size_t) (translator.d_input - constOctetCast(input)));

    if (translator.d_invalidSequences) {
        ret |= k_INVALID_INPUT_BIT;
    }

    return ret;
}

                     // =================================
                     // local class Utf32ToUtf8Translator
                     // =================================

/// This `class` is used to create an instance of an object that will be
/// used for translating a UTF-32 stream into UTF-8.  All functions in this
/// `class` are private except for a single static method, `translate`,
/// which creates an object of this type and calls manipulators on it.
///
/// The template argument `CAPACITY` is either `Capacity` or `NoopCapacity`
/// defined in the first unnamed namespace (above).  When the output is to a
/// buffer of fixed length that may not be of sufficient size, the
/// `Capacity` type is used and calculations are made incrementally to
/// constantly check if the end of the buffer has been reached.  If the
/// destination buffer is known ahead of time to be long enough, the
/// `NoopCapacity` type is used, and then all the incremental capacity
/// checking becomes inline calls to no-op functions that will generate no
/// code and waste no CPU time.
///
/// The template argument `END_FUNCTOR` is used for determining the end of
/// input.  If the input is null terminated, `Utf32ZeroBasedEnd` is used for
/// `END_FUNCTOR`; if the input is of fixed length, an instance of
/// `Utf32PtrBasedEnd`, which contains a pointer to the end of the input, is
/// used.
///
/// The template argument `SWAPPER` is used for dealing with the byte order
/// of the UTF-32 input.  If the input is in host byte order, a template
/// argument of `NoopSwapper` is used and the input is not swapped.  If the
/// input is in the opposite of host byte order, a template argument of
/// `Swapper` is used, which will swap the byte order of every 32-bit word
/// input to host byte order during the translator's internal processing.
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
class Utf32ToUtf8Translator {

    BSLMF_ASSERT((bsl::is_same<CAPACITY,        Capacity>::value ||
                  bsl::is_same<CAPACITY,    NoopCapacity>::value));

    BSLMF_ASSERT((bsl::is_same<END_FUNCTOR,  Utf32PtrBasedEnd>::value ||
                  bsl::is_same<END_FUNCTOR, Utf32ZeroBasedEnd>::value));

    BSLMF_ASSERT((bsl::is_same<SWAPPER,         Swapper>::value ||
                  bsl::is_same<SWAPPER,     NoopSwapper>::value));

    // DATA
    OctetType          *d_output;
    CAPACITY            d_capacity;
    const unsigned int *d_input;
    const OctetType     d_errorByte;
    bsl::size_t         d_numCodePointsWritten;
    bool                d_invalidSequences;

  private:
    // PRIVATE CREATOR

    /// Create a `Utf32ToUtf8Translator` object to translate UTF-32 from the
    /// specified `input` to be written as UTF-8 to the specified `output`
    /// buffer that is the specified `capacity` length in `unsigned int`s,
    /// or is guaranteed to have adequate room for the translation if
    /// `CAPACITY` is `NoopCapacity`.  Initialize `d_errorByte` to the
    /// specified `errorByte`.
    Utf32ToUtf8Translator(OctetType          *output,
                          bsl::size_t         capacity,
                          const unsigned int *input,
                          const OctetType     errorByte);

    // PRIVATE MANIPULATORS

    /// Update the state of this object to reflect the fact that a single
    /// Unicode code point of the specified `delta` bytes of UTF-8 output
    /// has been written.
    void advance(unsigned delta);

    /// Update this object and the output, if appropriate, to reflect the
    /// fact that an error sequence has been encountered in the input.
    /// Return a non-zero value if there was insufficient capacity for the
    /// output, and 0 otherwise.
    int handleInvalidWord();

    /// Translate the specified UTF-32 code point `uc` to UTF-8 in the
    /// output stream, updating this object appropriately.  If insufficient
    /// space exists in the output buffer for the code point output plus a
    /// terminating null code point, fail without doing any output.  Return
    /// 0 if there was adequate space for the output, and a non-zero value
    /// otherwise.  The behavior is undefined unless `uc` is non-zero, and
    /// there are at least 2 bytes of room in the output buffer.
    int decodeCodePoint(const unsigned int uc);

  public:
    // PUBLIC CLASS METHOD

    /// Translate a UTF-32 stream from the specified null-terminated `input`
    /// to a UTF-8 stream written to the buffer at the specified `output`,
    /// always null terminating the result.  If the template argument is
    /// type `Capacity`, the output buffer is the specified `capacity` bytes
    /// long, and encode as many Unicode code points as will fit, including
    /// the terminating null byte.  If the template argument is type
    /// `NoopCapacity`, `capacity` is ignored, the output buffer is assumed
    /// to be long enough, and the entire UTF-8 sequence is to be
    /// translated.  Call the specified `endFunctor` on the pointer to each
    /// word of `input` to be read, prior to dereferencing the pointer, to
    /// see if we've reached the end of input.  Write to the specified
    /// `*numCodePointsWritten` the number of Unicode code points written,
    /// including the terminating 0.  Write to the specified
    /// `*numBytesWritten` the number of bytes of output written, including
    /// the terminating null byte.  If the specified `errorByte` is
    /// non-zero, write `errorByte` to the output every time an error
    /// sequence is encountered in the input, otherwise write no output
    /// corresponding to error sequences in the input.  The behavior is
    /// undefined unless `CAPACITY` is `NoopCapacity` or `capacity > 0`.
    static
    int translate(char               *output,
                  bsl::size_t         capacity,
                  END_FUNCTOR         endFunctor,
                  const unsigned int *input,
                  bsl::size_t        *numCodePointsWritten,
                  bsl::size_t        *numBytesWritten,
                  const char          errorByte);
};

                     // ---------------------------------
                     // local class Utf32ToUtf8Translator
                     // ---------------------------------

// PRIVATE CREATORS
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
inline
Utf32ToUtf8Translator<CAPACITY, END_FUNCTOR, SWAPPER>::Utf32ToUtf8Translator(
                                                 OctetType          *output,
                                                 bsl::size_t         capacity,
                                                 const unsigned int *input,
                                                 const OctetType     errorByte)
: d_output(output)
, d_capacity(capacity)
, d_input(input)
, d_errorByte(errorByte)
, d_numCodePointsWritten(0)
, d_invalidSequences(false)
{}

// PRIVATE MANIPULATORS
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
inline
void Utf32ToUtf8Translator<CAPACITY, END_FUNCTOR, SWAPPER>::advance(
                                                                unsigned delta)
{
    d_capacity -= delta;
    d_output   += delta;
    ++d_numCodePointsWritten;
}

template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
inline
int Utf32ToUtf8Translator<CAPACITY, END_FUNCTOR, SWAPPER>::handleInvalidWord()
{
    d_invalidSequences = true;

    if (0 == d_errorByte) {
        return 0;                                                     // RETURN
    }

    if (d_capacity >= 2) {
        *d_output = d_errorByte;
        advance(1);
        return 0;                                                     // RETURN
    }
    else {
        return -1;                                                    // RETURN
    }
}

template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
int Utf32ToUtf8Translator<CAPACITY, END_FUNCTOR, SWAPPER>::decodeCodePoint(
                                                         const unsigned int uc)
{
    BSLS_ASSERT(d_capacity >= 1);

    if      (fitsInSingleOctet(uc)) {
        if (d_capacity >= 2) {
            *d_output = (OctetType) uc;
            advance(1);
        }
        else {
            return -1;                                                // RETURN
        }
    }
    else if (fitsInTwoOctets(uc)) {
        if (d_capacity >= 3) {
            encodeTwoOctets(d_output, uc);
            advance(2);
        }
        else {
            return -1;                                                // RETURN
        }
    }
    else if (fitsInThreeOctets(uc)) {
        if (isIllegal16BitValue(uc)) {
            return handleInvalidWord();                               // RETURN
        }
        else {
            if (d_capacity >= 4) {
                encodeThreeOctets(d_output, uc);
                advance(3);
            }
            else {
                return -1;                                            // RETURN
            }
        }
    }
    else if (fitsInFourOctets(uc)) {
        if (isIllegalFourOctetValue(uc)) {
            return handleInvalidWord();                               // RETURN
        }
        else if (d_capacity >= 5) {
            encodeFourOctets(d_output, uc);
            advance(4);
        }
        else {
            return -1;                                                // RETURN
        }
    }
    else {
        return handleInvalidWord();                                   // RETURN
    }

    return 0;
}

// CLASS METHODS
template <class CAPACITY, class END_FUNCTOR, class SWAPPER>
int Utf32ToUtf8Translator<CAPACITY, END_FUNCTOR, SWAPPER>::translate(
                                      char               *output,
                                      bsl::size_t         capacity,
                                      END_FUNCTOR         endFunctor,
                                      const unsigned int *input,
                                      bsl::size_t        *numCodePointsWritten,
                                      bsl::size_t        *numBytesWritten,
                                      const char          errorByte)
{
    BSLS_ASSERT(0 == (0x80 & errorByte));

    Utf32ToUtf8Translator<CAPACITY, END_FUNCTOR, SWAPPER> translator(
                                                             octetCast(output),
                                                             capacity,
                                                             input,
                                                             errorByte);
    BSLS_ASSERT(translator.d_capacity >= 1);

    int          ret = 0;
    unsigned int uc;
    while (!endFunctor.isFinished(translator.d_input)) {
        uc = SWAPPER::swapBytes(*translator.d_input++);
        if (0 != translator.decodeCodePoint(uc)) {
            BSLS_ASSERT((bsl::is_same<CAPACITY, Capacity>::value));
            ret |= k_OUT_OF_SPACE_BIT;
            break;
        }
    }
    BSLS_ASSERT(translator.d_capacity >= 1);

    *translator.d_output = 0;
    translator.advance(1);

    *numCodePointsWritten = translator.d_numCodePointsWritten;

    *numBytesWritten = translator.d_output - octetCast(output);
    BSLS_ASSERT((bsl::is_same<CAPACITY, NoopCapacity>::value) ||
                                                 *numBytesWritten <= capacity);
    BSLS_ASSERT((*numCodePointsWritten) * 4 - 3 >= *numBytesWritten);

    // Check for 0's in the middle of the output.

    BSLS_ASSERT((bsl::is_same<END_FUNCTOR, Utf32PtrBasedEnd>::value) ||
                                  bsl::strlen(output) == *numBytesWritten - 1);

    if (translator.d_invalidSequences) {
        ret |= k_INVALID_INPUT_BIT;
    }

    return ret;
}

template <class VECTOR>
int utf8ToUtf32Impl(VECTOR                              *dstVector,
                    const char                          *srcString,
                    unsigned int                         errorWord,
                    BloombergLP::bdlde::ByteOrder::Enum  byteOrder)
{
    typedef Utf8ToUtf32Translator<NoopCapacity,
                                  Utf8ZeroBasedEnd,
                                  Swapper> SwapTranslator;
    typedef Utf8ToUtf32Translator<NoopCapacity,
                                  Utf8ZeroBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    Utf8ZeroBasedEnd endFunctor;

    bsl::size_t bufferLen = utf32BufferLengthNeeded(srcString,
                                                    endFunctor);
    BSLS_ASSERT(bufferLen > 0);
    dstVector->resize(bufferLen);

    bsl::size_t numWordsWritten;

    int ret = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
              ? NoSwapTranslator::translate(&dstVector->front(),
                                            0,
                                            endFunctor,
                                            srcString,
                                            &numWordsWritten,
                                            errorWord)
              : SwapTranslator::translate(&dstVector->front(),
                                          0,
                                          endFunctor,
                                          srcString,
                                          &numWordsWritten,
                                          errorWord);

    // All asserts after this point are internal consistency checks that should
    // never fail in production, so they are for testing purposes only.

    BSLS_ASSERT(!(ret & k_OUT_OF_SPACE_BIT));
    BSLS_ASSERT(numWordsWritten >= 1);
    if (bufferLen > numWordsWritten) {
        // 'bufferLen' should have been an exactly accurate estimate unless
        // '0 == errorWord' and invalid code points occurred.

        BSLS_ASSERT(0 == errorWord);
        BSLS_ASSERT(ret & k_INVALID_INPUT_BIT);
        dstVector->resize(numWordsWritten);
    }
    else {
        // 'bufferLen' should never be an underestimate.

        BSLS_ASSERT(bufferLen == numWordsWritten);

        // If the estimate was spot on, either 'errorWord' was non-zero,
        // or no invalid code points occurred.

        BSLS_ASSERT(0 != errorWord || 0 == (ret & k_INVALID_INPUT_BIT));
    }

    BSLS_ASSERT(0 == dstVector->back());

    // Verify that the translator embedded no null words in the output before
    // the end.

    BSLS_ASSERT_SAFE(bsl::find(&dstVector->front(),
                               &dstVector->front() + numWordsWritten,
                               0) == &dstVector->back());

    return ret;
}

template <class VECTOR>
int utf8ToUtf32Impl(VECTOR                              *dstVector,
                    const bsl::string_view&              srcString,
                    unsigned int                         errorWord,
                    BloombergLP::bdlde::ByteOrder::Enum  byteOrder)
{
    typedef Utf8ToUtf32Translator<NoopCapacity,
                                  Utf8PtrBasedEnd,
                                  Swapper> SwapTranslator;
    typedef Utf8ToUtf32Translator<NoopCapacity,
                                  Utf8PtrBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    Utf8PtrBasedEnd endFunctor(srcString.data() + srcString.length());

    bsl::size_t bufferLen = utf32BufferLengthNeeded(srcString.data(),
                                                    endFunctor);
    BSLS_ASSERT(bufferLen > 0);
    dstVector->resize(bufferLen);

    bsl::size_t numWordsWritten;

    int ret = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
              ? NoSwapTranslator::translate(&dstVector->front(),
                                            0,
                                            endFunctor,
                                            srcString.data(),
                                            &numWordsWritten,
                                            errorWord)
              : SwapTranslator::translate(&dstVector->front(),
                                          0,
                                          endFunctor,
                                          srcString.data(),
                                          &numWordsWritten,
                                          errorWord);

    // All asserts after this point are internal consistency checks that should
    // never fail in production, so they are for testing purposes only.

    BSLS_ASSERT(!(ret & k_OUT_OF_SPACE_BIT));
    BSLS_ASSERT(numWordsWritten >= 1);
    if (bufferLen > numWordsWritten) {
        // 'bufferLen' should have been an exactly accurate estimate unless
        // '0 == errorWord' and invalid code points occurred.

        BSLS_ASSERT(0 == errorWord);
        BSLS_ASSERT(ret & k_INVALID_INPUT_BIT);
        dstVector->resize(numWordsWritten);
    }
    else {
        // 'bufferLen' should never be an underestimate.

        BSLS_ASSERT(bufferLen == numWordsWritten);

        // If the estimate was spot on, either 'errorWord' was non-zero,
        // or no invalid code points occurred.

        BSLS_ASSERT(0 != errorWord ||
                                             0 == (ret & k_INVALID_INPUT_BIT));
    }

    BSLS_ASSERT(0 == dstVector->back());

    return ret;
}

template <class STRING>
int utf32ToUtf8StringImpl(
                     STRING                              *dstString,
                     const unsigned int                  *srcString,
                     bsl::size_t                         *numCodePointsWritten,
                     unsigned char                        errorByte,
                     BloombergLP::bdlde::ByteOrder::Enum  byteOrder)
{
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32ZeroBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32ZeroBasedEnd,
                                  Swapper>   SwapTranslator;

    Utf32ZeroBasedEnd endFunctor;

    bsl::size_t bufferLen = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
               ? utf8BufferLengthNeeded<Utf32ZeroBasedEnd, NoopSwapper>(
                                                                    srcString,
                                                                    endFunctor,
                                                                    errorByte)
               : utf8BufferLengthNeeded<Utf32ZeroBasedEnd, Swapper>(srcString,
                                                                    endFunctor,
                                                                    errorByte);
    BSLS_ASSERT(bufferLen > 0);
    dstString->resize(bufferLen);
    BSLS_ASSERT(dstString->length() == bufferLen);

    bsl::size_t numBytesWritten, localNumCodePointsWritten;
    if (!numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    char *begin = &(*dstString)[0];
    int   ret = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
                ? NoSwapTranslator::translate(begin,
                                              0,
                                              endFunctor,
                                              srcString,
                                              numCodePointsWritten,
                                              &numBytesWritten,
                                              errorByte)
                : SwapTranslator::translate(begin,
                                            0,
                                            endFunctor,
                                            srcString,
                                            numCodePointsWritten,
                                            &numBytesWritten,
                                            errorByte);

    // The following are internal consistency checks that should never fail in
    // production, so they are for testing purposes only.

    BSLS_ASSERT(*numCodePointsWritten > 0);
    BSLS_ASSERT( numBytesWritten == bufferLen);
    BSLS_ASSERT(!(ret & k_OUT_OF_SPACE_BIT));
    BSLS_ASSERT(0 == (*dstString)[numBytesWritten - 1]);

    // There are two '\0's in 'dstString->c_str()' beginning at char
    // 'dstString->length() - 1' -- adjust 'length' to reflect the earlier
    // '\0'.

    dstString->resize(numBytesWritten - 1);

    return ret;
}

template <class STRING>
int utf32ToUtf8StringImpl(
                     STRING                              *dstString,
                     const unsigned int                  *srcString,
                     bsl::size_t                          srcStringLength,
                     bsl::size_t                         *numCodePointsWritten,
                     unsigned char                        errorByte,
                     BloombergLP::bdlde::ByteOrder::Enum  byteOrder)
{
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32PtrBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32PtrBasedEnd,
                                  Swapper>   SwapTranslator;

    Utf32PtrBasedEnd endFunctor(srcString + srcStringLength);

    bsl::size_t bufferLen = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
               ? utf8BufferLengthNeeded<Utf32PtrBasedEnd, NoopSwapper>(
                                                                    srcString,
                                                                    endFunctor,
                                                                    errorByte)
               : utf8BufferLengthNeeded<Utf32PtrBasedEnd, Swapper>(srcString,
                                                                   endFunctor,
                                                                   errorByte);
    BSLS_ASSERT(bufferLen > 0);
    dstString->resize(bufferLen);
    BSLS_ASSERT(dstString->length() == bufferLen);

    bsl::size_t numBytesWritten, localNumCodePointsWritten;
    if (!numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    char *begin = &(*dstString)[0];
    int   ret = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
                ? NoSwapTranslator::translate(begin,
                                              0,
                                              endFunctor,
                                              srcString,
                                              numCodePointsWritten,
                                              &numBytesWritten,
                                              errorByte)
                : SwapTranslator::translate(begin,
                                            0,
                                            endFunctor,
                                            srcString,
                                            numCodePointsWritten,
                                            &numBytesWritten,
                                            errorByte);

    // The following are internal consistency checks that should never fail in
    // production, so they are for testing purposes only.

    BSLS_ASSERT(*numCodePointsWritten > 0);
    BSLS_ASSERT( numBytesWritten == bufferLen);
    BSLS_ASSERT(!(ret & k_OUT_OF_SPACE_BIT));
    BSLS_ASSERT(0 == (*dstString)[numBytesWritten - 1]);

    // There are two '\0's in 'dstString->c_str()' beginning at char
    // 'dstString->length() - 1' -- adjust 'length' to reflect the earlier
    // '\0'.

    dstString->resize(numBytesWritten - 1);

    return ret;
}

template <class VECTOR>
int utf32ToUtf8VectorImpl(
                     VECTOR                              *dstVector,
                     const unsigned int                  *srcString,
                     bsl::size_t                         *numCodePointsWritten,
                     unsigned char                        errorByte,
                     BloombergLP::bdlde::ByteOrder::Enum  byteOrder)
{
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32ZeroBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32ZeroBasedEnd,
                                  Swapper>   SwapTranslator;

    bsl::size_t bufferLen = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
               ? utf8BufferLengthNeeded<Utf32ZeroBasedEnd, NoopSwapper>(
                                                           srcString,
                                                           Utf32ZeroBasedEnd(),
                                                           errorByte)
               : utf8BufferLengthNeeded<Utf32ZeroBasedEnd, Swapper>(
                                                           srcString,
                                                           Utf32ZeroBasedEnd(),
                                                           errorByte);
    BSLS_ASSERT(bufferLen > 0);
    dstVector->resize(bufferLen);

    bsl::size_t numBytesWritten, localNumCodePointsWritten;
    if (!numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    char *begin = &dstVector->front();
    int   ret   = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
                  ? NoSwapTranslator::translate(begin,
                                                0,
                                                Utf32ZeroBasedEnd(),
                                                srcString,
                                                numCodePointsWritten,
                                                &numBytesWritten,
                                                errorByte)
                  : SwapTranslator::translate(begin,
                                              0,
                                              Utf32ZeroBasedEnd(),
                                              srcString,
                                              numCodePointsWritten,
                                              &numBytesWritten,
                                              errorByte);

    // The following are internal consistency checks that should never fail in
    // production, so they are for testing purposes only.

    BSLS_ASSERT(*numCodePointsWritten > 0);
    BSLS_ASSERT( numBytesWritten > 0);
    BSLS_ASSERT(*numCodePointsWritten <= numBytesWritten);
    BSLS_ASSERT(0 == (ret & k_OUT_OF_SPACE_BIT));
    BSLS_ASSERT(numBytesWritten == bufferLen);
    BSLS_ASSERT(0 == dstVector->back());

    return ret;
}

template <class VECTOR>
int utf32ToUtf8VectorImpl(
                     VECTOR                              *dstVector,
                     const unsigned int                  *srcString,
                     bsl::size_t                          srcStringLength,
                     bsl::size_t                         *numCodePointsWritten,
                     unsigned char                        errorByte,
                     BloombergLP::bdlde::ByteOrder::Enum  byteOrder)
{
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32PtrBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    typedef Utf32ToUtf8Translator<NoopCapacity,
                                  Utf32PtrBasedEnd,
                                  Swapper>   SwapTranslator;

    Utf32PtrBasedEnd endFunctor(srcString + srcStringLength);

    bsl::size_t bufferLen = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
               ? utf8BufferLengthNeeded<Utf32PtrBasedEnd, NoopSwapper>(
                                                                    srcString,
                                                                    endFunctor,
                                                                    errorByte)
               : utf8BufferLengthNeeded<Utf32PtrBasedEnd, Swapper>(srcString,
                                                                   endFunctor,
                                                                   errorByte);
    BSLS_ASSERT(bufferLen > 0);
    dstVector->resize(bufferLen);

    bsl::size_t numBytesWritten, localNumCodePointsWritten;
    if (!numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    char *begin = &dstVector->front();
    int   ret   = BloombergLP::bdlde::ByteOrder::e_HOST == byteOrder
                  ? NoSwapTranslator::translate(begin,
                                                0,
                                                endFunctor,
                                                srcString,
                                                numCodePointsWritten,
                                                &numBytesWritten,
                                                errorByte)
                  : SwapTranslator::translate(begin,
                                              0,
                                              endFunctor,
                                              srcString,
                                              numCodePointsWritten,
                                              &numBytesWritten,
                                              errorByte);

    // The following are internal consistency checks that should never fail in
    // production, so they are for testing purposes only.

    BSLS_ASSERT(*numCodePointsWritten > 0);
    BSLS_ASSERT( numBytesWritten > 0);
    BSLS_ASSERT(*numCodePointsWritten <= numBytesWritten);
    BSLS_ASSERT(0 == (ret & k_OUT_OF_SPACE_BIT));
    BSLS_ASSERT(numBytesWritten == bufferLen);
    BSLS_ASSERT(0 == dstVector->back());

    return ret;
}

}  // close unnamed namespace

namespace BloombergLP {
namespace bdlde {

                          // -----------------------
                          // struct CharConvertUtf32
                          // -----------------------

                                  // UTF8 to UTF32

// CLASS METHODS
int CharConvertUtf32::utf8ToUtf32(bsl::vector<unsigned int> *dstVector,
                                  const char                *srcString,
                                  unsigned int               errorWord,
                                  ByteOrder::Enum            byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    return utf8ToUtf32Impl(dstVector, srcString, errorWord, byteOrder);
}

int CharConvertUtf32::utf8ToUtf32(std::vector<unsigned int> *dstVector,
                                  const char                *srcString,
                                  unsigned int               errorWord,
                                  ByteOrder::Enum            byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    return utf8ToUtf32Impl(dstVector, srcString, errorWord, byteOrder);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
int CharConvertUtf32::utf8ToUtf32(std::pmr::vector<unsigned int> *dstVector,
                                  const char                     *srcString,
                                  unsigned int                    errorWord,
                                  ByteOrder::Enum                 byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    return utf8ToUtf32Impl(dstVector, srcString, errorWord, byteOrder);
}
#endif

int CharConvertUtf32::utf8ToUtf32(bsl::vector<unsigned int> *dstVector,
                                  const bsl::string_view&    srcString,
                                  unsigned int               errorWord,
                                  ByteOrder::Enum            byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString.data());
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    return utf8ToUtf32Impl(dstVector, srcString, errorWord, byteOrder);
}

int CharConvertUtf32::utf8ToUtf32(std::vector<unsigned int> *dstVector,
                                  const bsl::string_view&    srcString,
                                  unsigned int               errorWord,
                                  ByteOrder::Enum            byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString.data());
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    return utf8ToUtf32Impl(dstVector, srcString, errorWord, byteOrder);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
int CharConvertUtf32::utf8ToUtf32(std::pmr::vector<unsigned int> *dstVector,
                                  const bsl::string_view&         srcString,
                                  unsigned int                    errorWord,
                                  ByteOrder::Enum                 byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString.data());
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    return utf8ToUtf32Impl(dstVector, srcString, errorWord, byteOrder);
}
#endif

int CharConvertUtf32::utf8ToUtf32(unsigned int          *dstBuffer,
                                  bsl::size_t            dstCapacity,
                                  const char            *srcString,
                                  bsl::size_t           *numCodePointsWritten,
                                  unsigned int           errorWord,
                                  ByteOrder::Enum        byteOrder)
{
    BSLS_ASSERT(dstBuffer);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    typedef Utf8ToUtf32Translator<Capacity,
                                  Utf8ZeroBasedEnd,
                                  Swapper> SwapTranslator;
    typedef Utf8ToUtf32Translator<Capacity,
                                  Utf8ZeroBasedEnd,
                                  NoopSwapper> NoSwapTranslator;

    Utf8ZeroBasedEnd endFunctor;

    bsl::size_t localNumCodePointsWritten;
    if (0 == numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    if (0 == dstCapacity) {
        *numCodePointsWritten = 0;
        return k_OUT_OF_SPACE_BIT;                                    // RETURN
    }

    return ByteOrder::e_HOST == byteOrder
           ? NoSwapTranslator::translate(dstBuffer,
                                         dstCapacity,
                                         endFunctor,
                                         srcString,
                                         numCodePointsWritten,
                                         errorWord)
           : SwapTranslator::translate(dstBuffer,
                                       dstCapacity,
                                       endFunctor,
                                       srcString,
                                       numCodePointsWritten,
                                       errorWord);
}

int CharConvertUtf32::utf8ToUtf32(
                                 unsigned int            *dstBuffer,
                                 bsl::size_t              dstCapacity,
                                 const bsl::string_view&  srcString,
                                 bsl::size_t             *numCodePointsWritten,
                                 unsigned int             errorWord,
                                 ByteOrder::Enum          byteOrder)
{
    BSLS_ASSERT(dstBuffer);
    BSLS_ASSERT(srcString.data());
    BSLS_ASSERT(isLegalUtf32ErrorWord(errorWord));

    typedef Utf8ToUtf32Translator<Capacity,
                                  Utf8PtrBasedEnd,
                                  Swapper> SwapTranslator;
    typedef Utf8ToUtf32Translator<Capacity,
                                  Utf8PtrBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    Utf8PtrBasedEnd endFunctor(srcString.data() + srcString.length());

    bsl::size_t localNumCodePointsWritten;
    if (0 == numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    if (0 == dstCapacity) {
        *numCodePointsWritten = 0;
        return k_OUT_OF_SPACE_BIT;                                    // RETURN
    }

    return ByteOrder::e_HOST == byteOrder
           ? NoSwapTranslator::translate(dstBuffer,
                                         dstCapacity,
                                         endFunctor,
                                         srcString.data(),
                                         numCodePointsWritten,
                                         errorWord)
           : SwapTranslator::translate(dstBuffer,
                                       dstCapacity,
                                       endFunctor,
                                       srcString.data(),
                                       numCodePointsWritten,
                                       errorWord);
}

                                // UTF32 to UTF8

int CharConvertUtf32::utf32ToUtf8(bsl::string           *dstString,
                                  const unsigned int    *srcString,
                                  bsl::size_t           *numCodePointsWritten,
                                  unsigned char          errorByte,
                                  ByteOrder::Enum        byteOrder)
{
    BSLS_ASSERT(dstString);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8StringImpl(dstString,
                                 srcString,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

int CharConvertUtf32::utf32ToUtf8(std::string           *dstString,
                                  const unsigned int    *srcString,
                                  bsl::size_t           *numCodePointsWritten,
                                  unsigned char          errorByte,
                                  ByteOrder::Enum        byteOrder)
{
    BSLS_ASSERT(dstString);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8StringImpl(dstString,
                                 srcString,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
int CharConvertUtf32::utf32ToUtf8(std::pmr::string   *dstString,
                                  const unsigned int *srcString,
                                  bsl::size_t        *numCodePointsWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstString);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8StringImpl(dstString,
                                 srcString,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}
#endif

int CharConvertUtf32::utf32ToUtf8(bsl::string           *dstString,
                                  const unsigned int    *srcString,
                                  bsl::size_t            srcStringLength,
                                  bsl::size_t           *numCodePointsWritten,
                                  unsigned char          errorByte,
                                  ByteOrder::Enum        byteOrder)
{
    BSLS_ASSERT(dstString);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8StringImpl(dstString,
                                 srcString,
                                 srcStringLength,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

int CharConvertUtf32::utf32ToUtf8(std::string           *dstString,
                                  const unsigned int    *srcString,
                                  bsl::size_t            srcStringLength,
                                  bsl::size_t           *numCodePointsWritten,
                                  unsigned char          errorByte,
                                  ByteOrder::Enum        byteOrder)
{
    BSLS_ASSERT(dstString);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8StringImpl(dstString,
                                 srcString,
                                 srcStringLength,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR_STRING
int CharConvertUtf32::utf32ToUtf8(std::pmr::string   *dstString,
                                  const unsigned int *srcString,
                                  bsl::size_t         srcStringLength,
                                  bsl::size_t        *numCodePointsWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstString);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8StringImpl(dstString,
                                 srcString,
                                 srcStringLength,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}
#endif

int CharConvertUtf32::utf32ToUtf8(bsl::vector<char>  *dstVector,
                                  const unsigned int *srcString,
                                  bsl::size_t        *numCodePointsWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8VectorImpl(dstVector,
                                 srcString,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

int CharConvertUtf32::utf32ToUtf8(std::vector<char>  *dstVector,
                                  const unsigned int *srcString,
                                  bsl::size_t        *numCodePointsWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8VectorImpl(dstVector,
                                 srcString,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
int CharConvertUtf32::utf32ToUtf8(std::pmr::vector<char> *dstVector,
                                  const unsigned int     *srcString,
                                  bsl::size_t            *numCodePointsWritten,
                                  unsigned char           errorByte,
                                  ByteOrder::Enum         byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8VectorImpl(dstVector,
                                 srcString,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}
#endif

int CharConvertUtf32::utf32ToUtf8(bsl::vector<char>  *dstVector,
                                  const unsigned int *srcString,
                                  bsl::size_t         srcStringLength,
                                  bsl::size_t        *numCodePointsWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8VectorImpl(dstVector,
                                 srcString,
                                 srcStringLength,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

int CharConvertUtf32::utf32ToUtf8(std::vector<char>  *dstVector,
                                  const unsigned int *srcString,
                                  bsl::size_t         srcStringLength,
                                  bsl::size_t        *numCodePointsWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8VectorImpl(dstVector,
                                 srcString,
                                 srcStringLength,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}

#ifdef BSLS_LIBRARYFEATURES_HAS_CPP17_PMR
int CharConvertUtf32::utf32ToUtf8(std::pmr::vector<char> *dstVector,
                                  const unsigned int     *srcString,
                                  bsl::size_t             srcStringLength,
                                  bsl::size_t            *numCodePointsWritten,
                                  unsigned char           errorByte,
                                  ByteOrder::Enum         byteOrder)
{
    BSLS_ASSERT(dstVector);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    return utf32ToUtf8VectorImpl(dstVector,
                                 srcString,
                                 srcStringLength,
                                 numCodePointsWritten,
                                 errorByte,
                                 byteOrder);
}
#endif

int CharConvertUtf32::utf32ToUtf8(char               *dstBuffer,
                                  bsl::size_t         dstCapacity,
                                  const unsigned int *srcString,
                                  bsl::size_t        *numCodePointsWritten,
                                  bsl::size_t        *numBytesWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstBuffer);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    bsl::size_t localNumCodePointsWritten, localNumBytesWritten;
    if (0 == numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    if (0 == numBytesWritten) {
        numBytesWritten = &localNumBytesWritten;
    }

    if (0 == dstCapacity) {
        *numCodePointsWritten = 0;
        *numBytesWritten = 0;
        return k_OUT_OF_SPACE_BIT;                                    // RETURN
    }

    typedef Utf32ToUtf8Translator<Capacity,
                                  Utf32ZeroBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    typedef Utf32ToUtf8Translator<Capacity,
                                  Utf32ZeroBasedEnd,
                                  Swapper>   SwapTranslator;

    int ret = ByteOrder::e_HOST == byteOrder
              ? NoSwapTranslator::translate(dstBuffer,
                                            dstCapacity,
                                            Utf32ZeroBasedEnd(),
                                            srcString,
                                            numCodePointsWritten,
                                            numBytesWritten,
                                            errorByte)
              : SwapTranslator::translate(dstBuffer,
                                          dstCapacity,
                                          Utf32ZeroBasedEnd(),
                                          srcString,
                                          numCodePointsWritten,
                                          numBytesWritten,
                                          errorByte);

    // The following are internal consistency checks that should never fail in
    // production, so they are for testing purposes only.

    BSLS_ASSERT(*numCodePointsWritten > 0);
    BSLS_ASSERT(*numBytesWritten > 0);
    BSLS_ASSERT(*numCodePointsWritten <= *numBytesWritten);
    BSLS_ASSERT(*numBytesWritten <= dstCapacity);
    BSLS_ASSERT(0 == dstBuffer[*numBytesWritten - 1]);

    return ret;
}

int CharConvertUtf32::utf32ToUtf8(char               *dstBuffer,
                                  bsl::size_t         dstCapacity,
                                  const unsigned int *srcString,
                                  bsl::size_t         srcStringLength,
                                  bsl::size_t        *numCodePointsWritten,
                                  bsl::size_t        *numBytesWritten,
                                  unsigned char       errorByte,
                                  ByteOrder::Enum     byteOrder)
{
    BSLS_ASSERT(dstBuffer);
    BSLS_ASSERT(srcString);
    BSLS_ASSERT(errorByte < 0x80);

    bsl::size_t localNumCodePointsWritten, localNumBytesWritten;
    if (0 == numCodePointsWritten) {
        numCodePointsWritten = &localNumCodePointsWritten;
    }
    if (0 == numBytesWritten) {
        numBytesWritten = &localNumBytesWritten;
    }

    if (0 == dstCapacity) {
        *numCodePointsWritten = 0;
        *numBytesWritten = 0;
        return k_OUT_OF_SPACE_BIT;                                    // RETURN
    }

    typedef Utf32ToUtf8Translator<Capacity,
                                  Utf32PtrBasedEnd,
                                  NoopSwapper> NoSwapTranslator;
    typedef Utf32ToUtf8Translator<Capacity,
                                  Utf32PtrBasedEnd,
                                  Swapper>   SwapTranslator;

    Utf32PtrBasedEnd endFunctor(srcString + srcStringLength);

    int ret = ByteOrder::e_HOST == byteOrder
              ? NoSwapTranslator::translate(dstBuffer,
                                            dstCapacity,
                                            endFunctor,
                                            srcString,
                                            numCodePointsWritten,
                                            numBytesWritten,
                                            errorByte)
              : SwapTranslator::translate(dstBuffer,
                                          dstCapacity,
                                          endFunctor,
                                          srcString,
                                          numCodePointsWritten,
                                          numBytesWritten,
                                          errorByte);

    // The following are internal consistency checks that should never fail in
    // production, so they are for testing purposes only.

    BSLS_ASSERT(*numCodePointsWritten > 0);
    BSLS_ASSERT(*numBytesWritten > 0);
    BSLS_ASSERT(*numCodePointsWritten <= *numBytesWritten);
    BSLS_ASSERT(*numBytesWritten <= dstCapacity);
    BSLS_ASSERT(0 == dstBuffer[*numBytesWritten - 1]);

    return ret;
}

}  // close package namespace
}  // close enterprise namespace

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
